#!/usr/bin/python3.9
# -*- coding: utf-8 -*-
# @Time    : 2021/4/20 10:06 上午
# @Author  : 姜晖
# @Email   : jianghui@tianyancha.com
# @File    : base_page.py
import platform
from contextlib import contextmanager
import allure
import xlrd
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.expected_conditions import staleness_of
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import os
import platform


class BasePage(object):

    """
    实现通用的Page方法，对常用自动化行为做封装
    减少每个Page对Appium，selenium等库的太多依赖
    """
    # 此处是黑名单机制，任何广告弹窗，tips等等都可以定义在这，形式如下：
    # [(By.ID, "image_cancel"),(By.ID, "tips")                   ]
    _black_list = [(By.CLASS_NAME, "close-btn")]

    def __init__(self, driver: WebDriver = None):
        self.driver = driver

    def find_element(self, locator, isPresence=False):
        try:
            if isPresence:
                WebDriverWait(self.driver, 20).until(EC.presence_of_element_located(locator))
                return self.driver.find_element(*locator)
            else:
                WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(locator))
                return self.driver.find_element(*locator)
        except:
            self.handle_exception()
            # 按理说应该这块是递归调用，但是这块要慎重处理，处理不好的话会出现死循环
            # self.find_element(locator)
            return self.driver.find_element(*locator)

    def find_elements(self, locator, isPresence=False):
        try:
            if isPresence:
                WebDriverWait(self.driver, 10).until(EC.presence_of_all_elements_located(locator))
                return self.driver.find_elements(*locator)
            else:
                WebDriverWait(self.driver, 10).until(EC.visibility_of_all_elements_located(locator))
                return self.driver.find_elements(*locator)
        except:
            self.handle_exception()
            return self.driver.find_elements(*locator)

    def action_chains(self, locator, index=0, no_click=False):
        ele = self.find_elements(locator)[index]
        if no_click:
            ActionChains(self.driver).move_to_element(ele).perform()
        else:
            ActionChains(self.driver).move_to_element(ele).click(ele).perform()

    # 此方法的好处是不仅仅在找元素的时候，处理异常，同时在点击的时候遇到任何问题，也会去看看是不是有什么干扰项。
    def find_element_and_click(self, locator, index=0):
        try:
            self.find_elements(locator)[index].click()
        except:
            self.handle_exception()
            self.find_elements(locator)[index].click()

    def find_element_and_click_by_js(self, locator, index=None):
        """
        通过执行javascript脚本达到定位元素并且点击的目的，主要是用来处理一些正常通过selenium定位并点击处理不了的场景，
        比如目前弹窗div中的checkbox控件的选中,目前此方法暂不做处理异常操作。
        :param locator: 定位符，元祖形式
        :param index:
        :return:
        """
        by_way = locator[0]
        by_locator = locator[1]
        if not index:
            if by_way == 'id':
                js_locator = 'getElementById({0})'.format(by_locator)
            elif by_way == 'css selector':
                js_locator = 'querySelector({0})'.format(by_locator)
            else:
                raise Exception('The locator you selected can not matched to js way, please change it')
            js_code = 'return document.{0}'.format(js_locator)
            element = self.driver.execute_script(js_code)
            self.driver.execute_script('arguments[0].click()', element)
        else:
            if by_way == 'class name':
                js_locator = 'getElementsByClassName("{0}")'.format(by_locator)
            elif by_way == 'name':
                js_locator = 'getElementsByName({0})'.format(by_locator)
            elif by_way == 'css selector':
                js_locator = 'querySelector({0})'.format(by_locator)
            else:
                raise Exception('The locator you selected can not matched to js way, please change it')
            js_code = 'return document.{0}'.format(js_locator)
            element = self.driver.execute_script(js_code)
            self.driver.execute_script('arguments[0].click()', element[index])

    def find_elements_and_click(self, locator, index=0):
        try:
            self.find_elements(locator)[index].click()
        except:
            self.handle_exception()
            self.find_elements(locator)[index].click()

    def find_elements_by_selenium_and_click_by_js(self, locator, index=None):
        by_way = locator[0]
        by_locator = locator[1]
        if index is not None:
            if by_way == 'xpath':
                js_locator = self.driver.find_elements_by_xpath(by_locator)[index]
            else:
                raise Exception('The locator you selected can not matched to js way, please change it')

            self.driver.execute_script('arguments[0].click()', js_locator)
        else:
            if by_way == 'xpath':
                js_locator = self.driver.find_element_by_xpath(by_locator)
            else:
                raise Exception('The locator you selected can not matched to js way, please change it')
            self.driver.execute_script('arguments[0].click()', js_locator)

    def find_element_and_input(self, locator, keyword, index=0):
        try:
            self.find_elements(locator)[index].clear()
            self.find_elements(locator)[index].send_keys(keyword)
        except:
            self.handle_exception()
            self.find_elements(locator)[index].clear()
            self.find_elements(locator)[index].send_keys(keyword)

    def get_current_url(self) -> str:
        """
        get current url of opening page
        """
        return self.driver.current_url

    @contextmanager
    def wait_for_page_load(self, element: WebElement = None):
        """
        Wait until page loaded, if element assigned, wait specific element loaded and otherwise wait full page
        :param element:
        :return:
        """
        old_page = self.driver.find_element_by_tag_name('html') if not element else element
        yield
        WebDriverWait(self.driver, 60).until(
            staleness_of(old_page),
            'The page {0} is not refreshed'.format(self.get_current_url())
        )

    def get_all_window_handles(self):
        return self.driver.window_handles

    def switch_to_window(self, window_name):
        self.driver.switch_to_window(window_name)

    def switch_to_div_alert(self):
        """
        This is made to handle those div kind of alert dialog
        """
        self.driver.switch_to.default_content()

    def handle_exception(self):
        for locator in self._black_list:
            elements = self.driver.find_elements(*locator)
            if len(elements) >= 1:
                elements[0].click()
            else:
                print("%s not found" % str(locator))
            # find_elements是重隐式等待的，如果黑名单的locator有的话会点击，但是如果没有的话，也会等待隐式等待设置的时间，所以可以先判断黑名单的项是否在page_source中
            # page_source = self._driver.page_source
            # if "image_cancel" in page_source:
            #     self._driver.find_element(*locator).click()
            # elif "tips" in page_source:
            #     pass

    def get_page_source(self):
        return self.driver.page_source

    def switch_to_new_window(self):
        """
        切换到新打开的窗口，此处添加等待是防止新窗口还未打开的时候，就做了切换处理而导致无法达到预期。
        :return:
        """
        handles = self.driver.window_handles
        try:
            WebDriverWait(self.driver, 5).until(EC.new_window_is_opened(handles))
            self.driver.switch_to.window(self.driver.window_handles[-1])
        except:
            self.driver.switch_to.window(self.driver.window_handles[-1])

    def switch_to_first_window(self):
        handles = self.driver.window_handles
        try:
            WebDriverWait(self.driver, 5).until(EC.new_window_is_opened(handles))
            self.driver.switch_to.window(self.driver.window_handles[0])
        except:
            self.driver.switch_to.window(self.driver.window_handles[0])

    def back_page(self):
        self.driver.back()

    def get_element_attribute(self, locator, attr, isvalueChange=False, value=""):
        element = self.find_element(locator)
        if isvalueChange:
            return WebDriverWait(self.driver, 20).until(lambda d : value == element.get_attribute(attr))
        else:
            return element.get_attribute(attr)

    def get_element_text(self, locator, index=0):
        element = self.find_elements(locator)[index]
        return element.text

    def get_element_all_text(self, locator, isset=False):
        if isset:
            return {element.text for element in self.find_elements(locator)}
        else:
            return [element.text for element in self.find_elements(locator)]

    def get_elements_attribute(self, locator, attr, index=0, presence=False):
        if presence:
            return self.find_element(locator, isPresence=True).get_attribute(attr)
        else:
            element = self.find_elements(locator)[index]
            return element.get_attribute(attr)

    def capture_screenshot_to_allure(self):
        screenshot = self.driver.get_screenshot_as_png()
        allure.attach(screenshot, '截图', allure.attachment_type.PNG)

    def get_local_storage(self, key):
        return self.driver.execute_script("return localStorage.getItem(arguments[0])", key)

    def set_local_storage(self, key, value):
        self.driver.execute_script("localStorage.setItem(arguments[0], arguments[1]);", key, value)

    def slide_page(self, locator, index=0):
        """等待页面滚动到对应元素可见"""
        target = self.find_elements(locator, isPresence=True)
        return self.driver.execute_script("arguments[0].scrollIntoView();", target[index])

    @contextmanager
    def switch_to_iframe(self, locator):
        """切换iframe"""
        self.driver.switch_to.frame(self.find_element(locator))
        yield
        self.driver.switch_to.default_content()

    # 此方法专门针对元素被定位后，内部的text属性还会加载变化的情况，如商家-设置-店铺信息-基本信息上方的三种状态
    # 页面初始化时元素就可定位，但是缺省text为"-"，需要等待一会才刷成对应的"已审核"等状态
    # occur参数表示终止条件是目标文本显示还是不显示，用find_elements方法兼容单个元素和元素列表的情况
    def wait_for_text(self, text, locator, occur=True, index=0):
        # 这里的find_elements调用的是selenium原生的，而不是BasePage封装的，所以必须解包传参
        if occur:
            WebDriverWait(self.driver, 5, 0.5)\
                .until(lambda d: text in d.find_elements(*locator)[index].text)
        else:
            WebDriverWait(self.driver, 5, 0.5)\
                .until_not(lambda d: text in d.find_elements(*locator)[index].text)

    # 与上面方法类似，此方法专门针对定位到元素后，元素内部的属性值会发生变化的情况
    # 具体可见 商家端-设置-店铺信息-签约信息-签约有效期中的时间控件，页面刚加载时此处显示的是当前时间
    # 加载完成后，才变成入驻时填写的签约时间，导致需要等待后才能进行字段校验
    def wait_for_attr(self, attrAndValue:'(attr, value)', locator, occur=True, index=0):
        if occur:
            WebDriverWait(self.driver, 5, 0.5)\
                .until(lambda d: attrAndValue[1] in d.find_elements(*locator)[index].get_attribute(attrAndValue[0]))
        else:
            WebDriverWait(self.driver, 5, 0.5) \
                .until_not(lambda d: attrAndValue[1] in d.find_elements(*locator)[index].get_attribute(attrAndValue[0]))

    def wait_for_disappear(self, locator):
        WebDriverWait(self.driver, 10).until(EC.staleness_of(self.find_element(locator)))

    # 元素是否存在
    def isElementExist(self, elements):
        driver = self.driver
        try:
            driver.find_elements_by_xpath(elements)
            return True
        except:
            return False

    def wait_html_change(self, attr, locator, index=0):
        WebDriverWait(self.driver, 20, 0.5).until(lambda d: attr != d.find_elements(*locator)[index].get_attribute("outerHTML"))

    def key_event(self, key, locator, index=0, time=1):
        for i in range(time):
            self.find_elements(locator)[index].send_keys(key)

    def keyboard_delete(self, locator, index=0, string=None):
        """
        清空输入框操作，全选之后再删除
        """
        ele = self.find_elements(locator)[index]
        if string:          # 适用于得先有内容，删除后才会出错误提示的输入框的验证
            ele.send_keys(string)
        ele.send_keys(Keys.COMMAND if platform.system() == "Darwin" else Keys.CONTROL, "a")
        return ele.send_keys(Keys.DELETE)

    # 等待元素消失
    def is_not_visible(self, locator, timeout=40):
        WebDriverWait(self.driver, timeout).until_not(EC.visibility_of_element_located(locator))

    # 判断元素是否存在
    def is_element_exist(self, locator):
        try:
            self.find_element(locator)
            return True
        except:
            return False


    # 读取文件内容
    def get_file_content(self, locator, sheet_name):
        # 读取表格内容
        table_open = xlrd.open_workbook(locator)
        # 获取sheet页的行列数
        table = table_open.sheet_by_name(sheet_name)
        return table.row_values(0)

    def refresh_page(self):
        self.driver.refresh()

    def element_text(self, locator):
        # 获取元素标签之间的文本
        return self.driver.find_element_by_xpath(locator).text

    def delete_n_times(self, locator, index=0, times=5, string=None):
        # 全选-删除的替代方法，因为前者有些场景不好使
        ele = self.find_elements(locator)[index]
        if string:          # 适用于得先有内容，删除后才会出错误提示的输入框的验证
            ele.send_keys(string)
        for i in range(times):
            ele.send_keys(Keys.BACK_SPACE)

    def move_im_page(self, locator):
        web_element = self.find_element(locator)
        ActionChains(self.driver).move_to_element(web_element).perform()

    def upload_file(self, locator, file_path, index=0, label="input"):
        """上传文件，如果是input，直接sendkeys"""
        if label == "input":
            return self.driver.find_element(*locator).send_keys(file_path)
        else:
            """"""
            # pyautogui.write(file_path)
            # pyautogui.press('enter', presses=2)
            pass

    def wait_for_display(self, locator, is_show=True):
        """
        :param locator:
        :param is_show: True-locator显示 False-locator在DOM中存在
        :return:
        """
        try:
            if is_show:
                WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(locator))
            else:
                WebDriverWait(self.driver, 20).until(EC.presence_of_element_located(locator))
        except:
            raise Exception("元素%s未找到" % locator)

    def wait_for_text_appear(self, locator, index=0):
        WebDriverWait(self.driver, 20).until(lambda d: d.find_elements(*locator)[index].text != '')
